package com.hyperexcel.utils;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.util.DateUtils;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.apache.poi.ss.usermodel.BorderStyle.DOTTED;

/**
 * Excel工具类
 *
 * @author: Linbizhao
 * @date 2024年08月29日 11:59
 */
public class HyperExcelUtils {

    private static final Logger log = LoggerFactory.getLogger(HyperExcelUtils.class);

    /**
     * 导出excel
     * @param list 导出列表
     * @param title 标题
     * @param sheetName sheet名称
     * @param pojoClass 导出实体类
     * @param fileName 文件名称
     * @param select 文件名称
     * @param response 响应
     */
    private static void exportByEasyExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, List<String> select, HttpServletResponse response) {
        try {
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
            if (StringUtils.isBlank(fileName)) {
                fileName = URLEncoder.encode(title + "_" + DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDateTime.now()), StandardCharsets.UTF_8);
            }
            response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8) + ".xlsx");
            // 头的策略
            WriteCellStyle headWriteCellStyle = new WriteCellStyle();
            headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
            WriteFont headWriteFont = new WriteFont();
            headWriteFont.setFontHeightInPoints((short) 10);
            headWriteCellStyle.setWriteFont(headWriteFont);
            // 内容策略
            WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
            // 设置自动换行
            contentWriteCellStyle.setWrapped(true);
            // 设置水平居中
            contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
            // 设置垂直居中
            contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
            contentWriteCellStyle.setBorderTop(DOTTED);
            contentWriteCellStyle.setBorderLeft(DOTTED);
            contentWriteCellStyle.setBorderRight(DOTTED);
            contentWriteCellStyle.setBorderBottom(DOTTED);
            // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
            HorizontalCellStyleStrategy cellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
            ExcelWriterSheetBuilder writerSheetBuilder = EasyExcel.write(response.getOutputStream(), pojoClass).sheet(sheetName).registerWriteHandler(cellStyleStrategy);
            if (CollectionUtils.isNotEmpty(select)) {
                writerSheetBuilder.head(getSelectHead(pojoClass, select));
                writerSheetBuilder.doWrite(getSelectData(list, pojoClass, select));
            } else {
                writerSheetBuilder.doWrite(list);
            }
        } catch (IOException e) {
            log.error("exportByEasyExcel.fail: ", e);
            throw new RuntimeException("exportByEasyExcel fail!");
        }
    }

    private static List<List<String>> getSelectHead(Class<?> pojoClass, List<String> select) {
        List<List<String>> list = new ArrayList<>();
        for (String column : select) {
            try {
                Field declaredField = pojoClass.getDeclaredField(column);
                String headName = declaredField.getAnnotation(ExcelProperty.class).value()[0];
                list.add(Collections.singletonList(headName));
            } catch (NoSuchFieldException e) {
                log.error("NoSuchFieldException: ", e);
                throw new RuntimeException(e);
            }
        }
        return list;
    }

    private static List<List<String>> getSelectData(List<?> list, Class<?> pojoClass, List<String> select) {
        List<List<String>> dataList = new ArrayList<>();
        List<Field> dataFieldList = new ArrayList<>();
        Map<String, String> dateTimeFormatMap = new HashMap<>();
        for (String column : select) {
            try {
                Field declaredField = pojoClass.getDeclaredField(column);
                DateTimeFormat dateTimeFormat = declaredField.getAnnotation(DateTimeFormat.class);
                if (dateTimeFormat != null) {
                    dateTimeFormatMap.put(column, dateTimeFormat.value());
                }
                dataFieldList.add(declaredField);
            } catch (NoSuchFieldException e) {
                log.error("NoSuchFieldException: ", e);
                throw new RuntimeException(e);
            }
        }

        for (Object o : list) {
            List<String> dataRow = new ArrayList<>();
            for (Field field : dataFieldList) {
                field.setAccessible(true);
                try {
                    Object fieldObj = field.get(o);
                    if (fieldObj == null) {
                        fieldObj = " ";
                    }
                    String format = dateTimeFormatMap.get(field.getName());
                    if (StringUtils.isNotBlank(format) && fieldObj instanceof Date) {
                        dataRow.add(DateUtils.format((Date) fieldObj, format));
                    } else {
                        dataRow.add(String.valueOf(fieldObj));
                    }
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
            dataList.add(dataRow);
        }
        return dataList;
    }

    public static void exportByEasyExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, HttpServletResponse response) {
        exportByEasyExcel(list, title, sheetName, pojoClass, fileName, null, response);
    }

    public static void exportByEasyExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, HttpServletResponse response) {
        exportByEasyExcel(list, title, sheetName, pojoClass, null, response);
    }

    public static void exportByEasyExcel(List<?> list, String title, Class<?> pojoClass, HttpServletResponse response) {
        exportByEasyExcel(list, title, title, pojoClass, null, response);
    }

    public static void exportByEasyExcel(List<?> list, String title, Class<?> pojoClass, List<String> select, HttpServletResponse response) {
        exportByEasyExcel(list, title, title, pojoClass, title, select, response);
    }

    /**
     * 同步导入，加数量限制
     * @param inputStream
     * @param pojoClass
     * @param limitNum
     * @param <T>
     * @return
     */
    public static <T> List<T> importByEasyExcel(InputStream inputStream, Class<T> pojoClass, int limitNum, boolean throwIfOutOfRange) {
        List<T> list = new ArrayList<>();
        boolean[] outOfRange = {false};
        EasyExcel.read(inputStream, pojoClass, new AnalysisEventListener<T>() {
            @Override
            public void invoke(T data, AnalysisContext context) {
                if (list.size() >= limitNum) {
                    // 达到限制数量
                    outOfRange[0] = true;
                    return;
                }
                list.add(data);
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {

            }
        }).sheet().doRead();

        if (throwIfOutOfRange && outOfRange[0]) {
            throw new RuntimeException("excel.error.out-of-range");
        }

        return list;
    }
}
